/*********************************************************************************
 * 
 *  ┌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┐
 *  ╎                                                                          ╎
 *  ╎                    w-validator 适用于 Koa 的强大参数验证器                  ╎
 *  ╎                            Author: Wang Qiang                            ╎
 *  ╎                                                                          ╎
 *  └╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌┘
 * 
 *********************************************************************************/
const lodash_1 = require("lodash");
const exception_1 = require("./exception");
const validator_1 = require("validator");
const extended_validator_1 = require("./extended-validator");
const util_1 = require("./util");
/**
 * 强大的校验器
 * 支持optional，支持array，支持nested object
 */
class Wvalidator {
  constructor() {
      /**
       * 装载数据的容器
       */
      this.data = {};
      /**
       * 解析后的数据容器
       */
      this.parsed = {};
      /**
       * 数据校验错误容器
       */
      this.errors = [];
  }
  /**
   * 校验
   * @param ctx koa context
   * @param alias 别名
   */
  async validate(ctx, alias) {
      this.alias = alias;
      this.data = {
          body: ctx.request.body,
          query: ctx.request.query,
          path: ctx.params,
          header: ctx.request.header
      };
      const tmpData = lodash_1.cloneDeep(this.data);
      this.parsed = Object.assign(Object.assign({}, tmpData), { default: {} });
      if (!(await this.checkRules())) {
          let obj = {};
          if (this.errors.length === 1) {
              obj = this.errors[0].message;
          }
          else {
              for (const err of this.errors) {
                  obj[err.key] = err.message;
              }
          }
          throw new exception_1.ParamsException({ errMsg: obj });
      }
      else {
          ctx.v = this;
          return this;
      }
  }
  replace(keys) {
      // key 是原来的名字
      if (!this.alias) {
          return keys;
      }
      let arr = [];
      for (let key of keys) {
          if (this.alias[key]) {
              this[this.alias[key]] = this[key];
              lodash_1.unset(this, key);
              arr.push(this.alias[key]);
          }
          else {
              arr.push(key);
          }
      }
      return arr;
  }
  isOptional(val) {
      // undefined , null , ""  , "    ", 皆通过
      if (val === void 0) {
          return true;
      }
      if (val === null) {
          return true;
      }
      if (typeof val === 'string') {
          return val === '' || val.trim() === '';
      }
      return false;
  }
  async checkRules() {
      // 筛选出是Rule或Rules的key
      // 添加规则校验 validateKey
      // default校验规则 throw
      let keys = util_1.getAllFieldNames(this, {
          filter: key => {
              const value = this[key];
              if (lodash_1.isArray(value)) {
                  if (value.length === 0) {
                      return false;
                  }
                  for (const it of value) {
                      if (!(it instanceof Rule)) {
                          throw new Error('every item must be a instance of Rule');
                      }
                  }
                  return true;
              }
              else {
                  return value instanceof Rule;
              }
          }
      });
      // 此处进行别名替换
      keys = this.replace(keys);
      for (const key of keys) {
          // 标志位
          let stoppedFlag = false;
          let optional = false;
          const value = this[key];
          let defaultVal;
          // 如果没有传入这个参数，检查这个校验链中是否有 isOptional 如果有通过，否则抛异常
          // 去data下找key，二级目录查找 dataKey 为一级目录的路径
          const [dataKey, dataVal] = this.findInData(key);
          if (this.isOptional(dataVal)) {
              let msg;
              if (lodash_1.isArray(value)) {
                  for (const it of value) {
                      if (it.optional) {
                          defaultVal = it.defaultValue && it.defaultValue;
                          optional = true;
                      }
                      else {
                          if (!msg) {
                              msg = it.message;
                          }
                      }
                  }
              }
              else {
                  if (value.optional) {
                      defaultVal = value.defaultValue && value.defaultValue;
                      optional = true;
                  }
                  else {
                      msg = value.message;
                  }
              }
              // 没有 isOptional 抛异常
              // 有 isOptional 取它的默认值
              if (!optional) {
                  this.errors.push({ key, message: msg || `${key}不可为空` });
              }
              else {
                  this.parsed['default'][key] = defaultVal;
              }
          }
          else {
              if (lodash_1.isArray(value)) {
                  const errs = [];
                  for (const it of value) {
                      // 当rule的optional为false时，进行校验
                      if (!stoppedFlag && !it.optional) {
                          let valid;
                          valid = await it.validate(this.data[dataKey][key]);
                          if (!valid) {
                              errs.push(it.message);
                              // 如果当前key已有错误，则置stoppedFlag为true，后续会直接跳过校验
                              stoppedFlag = true;
                          }
                      }
                      if (it.parsedValue !== void 0) {
                          this.parsed[dataKey][key] = it.parsedValue;
                      }
                  }
                  if (errs.length !== 0) {
                      this.errors.push({ key, message: errs });
                  }
              }
              else {
                  const errs = [];
                  if (!stoppedFlag && !value.optional) {
                      let valid;
                      valid = await value.validate(this.data[dataKey][key]);
                      if (!valid) {
                          errs.push(value.message);
                          // 如果当前key已有错误，则置stoppedFlag为true，后续会直接跳过校验
                          stoppedFlag = true;
                      }
                  }
                  if (value.parsedValue !== void 0) {
                      this.parsed[dataKey][key] = value.parsedValue;
                  }
                  if (errs.length !== 0) {
                      this.errors.push({ key, message: errs });
                  }
              }
          }
      }
      let validateFuncKeys = util_1.getAllMethodNames(this, {
          filter: key => /validate([A-Z])\w+/g.test(key) && typeof this[key] === 'function'
      });
      for (const validateFuncKey of validateFuncKeys) {
          //  最后校验规则函数
          const customerValidateFunc = lodash_1.get(this, validateFuncKey);
          // 规则函数，每个都try,catch，并将错误信息加入到整体错误信息中
          // 第一个参数为data
          // 自定义校验函数，第一个参数是校验是否成功，第二个参数为错误信息
          let validRes;
          try {
              validRes = await customerValidateFunc.call(this, this.data);
              if (lodash_1.isArray(validRes) && !validRes[0]) {
                  let key;
                  if (validRes[2]) {
                      key = validRes[2];
                  }
                  else {
                      key = this.getValidateFuncKey(validateFuncKey);
                  }
                  this.errors.push({ key, message: validRes[1] });
              }
              else if (!validRes) {
                  let key = this.getValidateFuncKey(validateFuncKey);
                  // 如果自定函数没有给出错误信息，那么错误信息为默认
                  this.errors.push({ key, message: '参数错误' });
              }
          }
          catch (error) {
              const key = this.getValidateFuncKey(validateFuncKey);
              if (error instanceof exception_1.HttpException) {
                  this.errors.push({ key, message: error.msg });
              }
              else {
                  this.errors.push({ key, message: error.message });
              }
          }
      }
      return this.errors.length === 0;
  }
  /**
   * 获得规则函数的key
   * @param validateFuncKey 规则函数的名称
   */
  getValidateFuncKey(validateFuncKey) {
      return validateFuncKey.replace('validate', '');
  }
  /**
   *  取参数里的值；如果参数不能被解析，则返回没有被解析的值
   * @param path 参数所在的路径，如 a.b
   * @param parsed 是否取已经解析后的数据，默认为true
   * @param defaultVal 默认值，当路径指向的值不存在，取默认值
   */
  get(path, parsed = true) {
      let defaultVal;
      if (arguments.length >= 3) {
          defaultVal = arguments[2];
      }
      if (parsed) {
          const key = lodash_1.get(this.parsed, path, defaultVal && defaultVal);
          if (!this.isOptional(key)) {
              return key;
          }
          else {
              const index = path.lastIndexOf('.');
              const suffix = path.substring(index + 1, path.length);
              return lodash_1.get(this.parsed['default'], suffix, defaultVal && defaultVal);
          }
      }
      else {
          return lodash_1.get(this.data, path, defaultVal && defaultVal);
      }
  }
  findInData(key) {
      const keys = Object.keys(this.data);
      for (const k of keys) {
          const val = lodash_1.get(this.data[k], key);
          if (val !== void 0) {
              return [k, val];
          }
      }
      return [];
  }
}

/**
* 规则类
*/
class Rule {
  constructor(validateFunction, message, ...options) {
      this.optional = false;
      this.validateFunction = validateFunction;
      this.message = message || '参数错误';
      this.options = options;
      if (this.validateFunction === 'isOptional') {
          // 如果当前项为optional，检查该项是否存在，若不存在，将optional置为true
          // 如果是optional，那么没有传入的参数，可以使用默认值
          this.optional = true;
          this.defaultValue = options && options[0];
      }
  }
  validate(value) {
      this.rawValue = value;
      if (typeof this.validateFunction === 'function') {
          return this.validateFunction(value, ...this.options);
      }
      else {
          switch (this.validateFunction) {
              case 'isInt':
                  if (typeof value === 'string') {
                      this.parsedValue = validator_1.default.toInt(value);
                      return validator_1.default.isInt(value, ...this.options);
                  }
                  else {
                      this.parsedValue = value;
                      return validator_1.default.isInt(String(value), ...this.options);
                  }
              case 'isFloat':
                  if (typeof value === 'string') {
                      this.parsedValue = validator_1.default.toFloat(value);
                      return validator_1.default.isFloat(value, ...this.options);
                  }
                  else {
                      this.parsedValue = value;
                      return validator_1.default.isFloat(String(value), ...this.options);
                  }
              case 'isBoolean':
                  if (typeof value === 'string') {
                      this.parsedValue = validator_1.default.toBoolean(value);
                      return validator_1.default.isBoolean(value);
                  }
                  else {
                      this.parsedValue = value;
                      return validator_1.default.isBoolean(String(value));
                  }
              case 'isNotEmpty':
                  return extended_validator_1.extendedValidator.isNotEmpty(value);
              default:
                  return validator_1.default[this.validateFunction](value, ...this.options);
          }
      }
  }
}

module.exports = {
  Wvalidator,
  Rule
}
